/*
 * Blocksort.cpp
 *
 *  Created on: May 21, 2012
 *      Author: Nico
 */

#ifndef BLOCKSORT_H_
#define BLOCKSORT_H_

#include "includes.h"
#include "compressedStream.cpp"
#include "Blocksort.h"


/* Metodos publicos */

/* public */ void Blocksort::setInputFilename(const char* filename){
	this->inputFilename = (char*)filename;
}

/* public */ void Blocksort::setOutputFilename(const char* filename){
	this->outputFilename = (char*)filename;
}




BlocksortCompress::BlocksortCompress(){
	this->init();
}

BlocksortCompress::BlocksortCompress(const char* inputFilename , const int blockSize){
	this->init();
	
	this->blockSize = blockSize;

	this->lastReadSize = blockSize;

	this->readingFromFile = true;
	this->imAtFirstBlock  = true;

	this->setInputFilename(inputFilename);
	this->setOutputFilename("Blocksort.output.txt");
}

/* private */ void BlocksortCompress::init(){
	
	// Debe estar activado solo durante la ejecucion del
	// metodo Blocksort::runNextBlock()
	this->readingFromFile = false;
	
	this->debug = false;
}

BlocksortCompress::~BlocksortCompress(){
	
	if( this->inputFile.is_open() ){
		this->inputFile.close();
	}
	
	if( this->readingFromFile == true ){
		delete this->outputFile;
	}
	/*
	if( this->outputFile.is_open() ){
		this->outputFile.close();
	}
	*/
}



/* Metodos publicos */


/* public */ int BlocksortCompress::runBlock(const char* texto , string* L, uInt* I ){
	
	// Me aseguro un string inicialmente en blanco
	L->clear();
	
	if( this->readingFromFile == false ){
		// Si se estuviera leyendo desde archivo, esto se 
		// define en la lectura de cada bloque
		this->lastReadSize = strlen(texto);
		this->blockFirstChar = (char*)texto;
		this->blockLastChar  = (char*)texto + this->lastReadSize - 1;
		
		if(this->debug){
			printf("Texto: '%s'\n\n",texto);
		}
	}
	
	if(this->debug){
		printf("ReadSize: %d\n\n",this->lastReadSize);
	}

	vector<parOrdenado*> rotaciones;
	vector<parOrdenado*>::iterator iterador;
	
	this->spinText( texto , &rotaciones ); 
	
	// Debug
	if(this->debug){
		for(iterador = rotaciones.begin(); iterador < rotaciones.end(); iterador++){
			printf( "(%d , %d)\n" , (uInt)(**iterador).indexStart , (uInt)(**iterador).indexEnd );
			printf( "Interpreto Par ordenado: '" ); this->debug_parOrdenado(*iterador); printf( "'\n\n" );
			//delete *iterador;
		}
	}
	
	sortParOrdenado s(this);
	std::sort( rotaciones.begin() , rotaciones.end() , s );

	// Debug
	if(this->debug){
		printf("Ordeno.......................................\n\n\n");
		
		for(iterador = rotaciones.begin(); iterador < rotaciones.end(); iterador++){
			printf( "(%d , %d)\n" , (uInt)(**iterador).indexStart , (uInt)(**iterador).indexEnd );
			printf( "Interpreto Par ordenado: '" ); this->debug_parOrdenado(*iterador); printf( "'\n\n" );
		}
	}


	this->findLandI( &rotaciones , L , I );
	

	// Libero memoria (borro todos los pares ordenados)
	for(iterador = rotaciones.begin(); iterador < rotaciones.end(); iterador++){
		delete *iterador;
	}
	
	return 0;
}

/* public */ int BlocksortCompress::runNextBlock(string* L, uInt* I ){
	if( this->imAtFirstBlock == true ){
		this->imAtFirstBlock = false;
		this->outputFile = new OCompressedStream( this->outputFilename , this->blockSize );
	}
	uInt status;
	char* texto = (char*)malloc( this->blockSize * sizeof(char) );

	status = this->getNextBlockOfText(&texto);

	if( status == ERROR_LECTURA ){
		//cout << "Error de lectura." << endl;
		free(texto);
		return ERROR_LECTURA;

	}else if( status == ERROR_EOF ){
		//cout << "End of file." << endl;
		free(texto);
		return ERROR_EOF;
	}

	// Debug
	if(this->debug){
		printf( "Texto leido: '%s'\n" , texto );
		printf("BlockSize: %d\n",this->blockSize);
	}
	
	
	runBlock( texto , L, I );
	
	//cout << "Escribiendo archivo " << this->outputFilename << endl;
	this->outputFile->writeBlockSortingResult( L , *I );
	
	free(texto);
	
	return 0;
}

/* public */ int BlocksortCompress::run(){
	string L;
	uInt  I;
	while( ERROR_EOF != this->runNextBlock( &L, &I ) ){
		if(this->debug){
			printf( "I: %d \n" , I );
			printf( "L: '%s'\n\n" , L.c_str() );
		}
	}
	return 0;
}

/* Metodos protegidos */

/* protected */ void BlocksortCompress::debug_parOrdenado(parOrdenado* par){
	
	char* start = par->indexStart;
	char* end   = par->indexEnd;
	
	char* iterador = start;
	
	//string s;
	
	while(true){
		
		// Imprimo el caracter apuntado
		printf( "%c" , *iterador );
		
		// Comparo/Asigno posiciones de memoria
		if( iterador == end ){
			break;
		}else if( iterador == this->blockLastChar ){
			iterador = this->blockFirstChar;
		}else{
			iterador++;
		}
		
		//getline(cin, s);
		
	}
}

/* protected */ bool BlocksortCompress::cmpParOrdenado(const parOrdenado* a, const parOrdenado* b){
	
	char* aStart = a->indexStart;
	char* bStart = b->indexStart;
	
	char* aEnd = a->indexEnd;
	char* bEnd = b->indexEnd;
	
	char* iteratorA = aStart;
	char* iteratorB = bStart;
	
	while(true){
		// Comparo el dato apuntado
		if( *iteratorA < *iteratorB ){
			return true;
		}else if( *iteratorA > *iteratorB ){
			return false;
		}
		
		// Comparo/Asigno posiciones de memoria
		if( iteratorA == aEnd ){
			break;
		}else if( iteratorA == this->blockLastChar ){
			iteratorA = this->blockFirstChar;
		}else{
			iteratorA++;
		}
				
		// Comparo/Asigno posiciones de memoria
		if( iteratorB == bEnd ){
			break;
		}else if( iteratorB == this->blockLastChar ){
			iteratorB = this->blockFirstChar;
		}else{
			iteratorB++;
		}
		
	}
	
	return false;
}


/* protected */ int BlocksortCompress::getNextBlockOfText(char** text){
	if( !this->inputFile.is_open() ){
		this->inputFile.open( this->inputFilename , ios::binary );
		if( !this->inputFile.is_open() ) return ERROR_FILE_NOT_EXIST;
		this->readingFromFile = true;
	}

	if( this->inputFile.eof() ){
		//cout << "Error de lectura." << endl;
		return ERROR_EOF;
	}

	this->inputFile.read( *text , this->blockSize );

	
	
	this->lastReadSize =  this->inputFile.gcount();
	if(debug) printf( "inputFile.gcount(): %d\n" , this->inputFile.gcount() );
	if(debug) printf( "strlen(text): %d\n" , strlen(*text) );
	if( this->lastReadSize == this->blockSize ){
		// Fix para ignorar caracter \0 al final del bloque
		if(debug) var_dump_string( *text , this->blockSize );
		if( last_char_is_null( *text , this->blockSize ) ){
			this->lastReadSize--;
			if(debug) printf("Ultimo char era NULL, lo ignoro.\n");
		}
	}else if( this->lastReadSize < this->blockSize ){
		// Fix para ignorar caracter \0 al final del bloque
		if(debug) printf( "this->lastReadSize < this->blockSize (%d < %d) entonces this->lastReadSize: %d\n" , this->lastReadSize , this->blockSize , this->lastReadSize-1 );
		this->lastReadSize--;
	}

	if( this->inputFile.bad() ){
		//cout << "Error de lectura." << endl;
		return ERROR_LECTURA;
	}
	
	// Guardo posicion inicial y final de memoria
	this->blockFirstChar = *text;
	this->blockLastChar  = *text + this->lastReadSize - 1;

	return 0;
}

/* protected */ void BlocksortCompress::spinText( const char* texto , vector<parOrdenado*>* rotaciones ){
	/* Guardo direcciones de memoria */
	char* indexStart = (char*)texto;
	for( uInt i = 0 ; i < this->lastReadSize ; i++ ){
		parOrdenado* par = new parOrdenado;
		
		par->indexStart = indexStart + i;
		par->indexEnd   = indexStart + ( (i + this->lastReadSize - 1) % this->lastReadSize );
		
		rotaciones->push_back(par);
	}
}

/* protected */ void BlocksortCompress::findLandI( /* const */ vector<parOrdenado*>* rotacionesOrdenadas, string* L , uInt* I ){
	// L es la concatenacion de todos los ultimos caracteres de cada rotacion
	// I es el indice donde se encuentra el string original
	
	vector<parOrdenado*>::iterator iterador;
	*I = 0;
	bool incrementar_i = true;
	for(iterador = rotacionesOrdenadas->begin(); iterador < rotacionesOrdenadas->end(); iterador++){
		// Comparo direcciones de memoria
		if( incrementar_i ){
			if( ( (*iterador)->indexStart == this->blockFirstChar ) and ( (*iterador)->indexEnd == this->blockLastChar ) ){
				incrementar_i = false;
			}else{
				(*I)++;
			}
		}
		L->append( 1 , *(*iterador)->indexEnd );
	}
}






BlocksortDecompress::BlocksortDecompress(const char* inputFilename){
	this->inputFile = new ICompressedStream( inputFilename );
	//this->init();

	this->readingFromFile = true;
	this->debug = false;

	this->setInputFilename(inputFilename);
	this->setOutputFilename("Blocksort.output.txt");
	
	
}


BlocksortDecompress::~BlocksortDecompress(){
	delete this->inputFile;

	if( this->outputFile.is_open() ){
		this->outputFile.close();
	}
}


/* public */ int BlocksortDecompress::runBlock( const char* L, const uInt I , const uInt blockLength , string* texto ){
	texto->clear();
	
	vector<caracterIndice> T;

	char* iteradorL;
	uInt length = 0;
	caracterIndice tupla;
	
	

	iteradorL = (char*) L ;
	for( uInt i=0 ; i < blockLength ; i++ ){
		
		tupla.caracter = iteradorL;
		tupla.indice = length;
		
		T.push_back( tupla );
		length++;
		
		iteradorL++;
	}

	// Ordeno
	std::sort( T.begin() , T.end() , cmpCaracterIndice );
	
	uInt index = I;
	for( uInt cont=0 ; cont < length ; cont++ ){	
		tupla = T.at(index);
		index = tupla.indice;
		texto->push_back( L[index] );
	}
	
	return 0;
}

/* public */ int BlocksortDecompress::runNextBlock( string* texto ){
	
	if( !this->outputFile.is_open() ){
		this->outputFile.open( this->outputFilename , ios::binary );
		if( !this->outputFile.is_open() ) return ERROR_FILE_NOT_EXIST;
	}
	
	texto->clear();
	
	string L; uInt I;
	
	bool is_eof = false;
	
	if( ERROR_EOF == this->inputFile->readBlockSortingResult( &L , &I ) ){
		if( L.length() == 0 ){
			return ERROR_EOF;
		}else{
			// Este caso se da cuando el buffer leido 
			// es menor al tamanio del bloque, y puede pasar solo 1 vez
			// al final del archivo
			is_eof = true;
		}
	}
	
	this->runBlock( L.c_str() , I , this->inputFile->lastReadAmount , texto );
	
	this->outputFile << *texto;
	
	if( is_eof ){
		return ERROR_EOF;
	}
	
	return 0;
}

/* public */ int BlocksortDecompress::run(){
	string texto;
	
	while( ERROR_EOF != this->runNextBlock( &texto ) ){
		if(this->debug) cout << "'" << texto << "'" << endl;
	}
	if(this->debug) cout << "'" << texto << "'" << endl;
	return 0;
}
#endif /* BLOCKSORT_H_ */
